<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>9种图表展示系统（前后端交互版）</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.1/dist/echarts.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.13/lib/theme-chalk/index.css">
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.13/lib/index.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}

body {
background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
min-height: 100vh;
padding: 20px;
}

#app {
max-width: 1800px;
margin: 0 auto;
}

.header {
background: linear-gradient(90deg, #1e5799 0%, #207cca 51%, #2989d8 100%);
color: white;
padding: 20px 30px;
border-radius: 12px 12px 0 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
margin-bottom: 25px;
display: flex;
justify-content: space-between;
align-items: center;
}

.header h1 {
font-size: 2.2rem;
display: flex;
align-items: center;
gap: 12px;
}

.header h1 i {
color: #ffd04b;
font-size: 2.5rem;
}

.controls {
display: flex;
gap: 15px;
}

.chart-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 25px;
margin-bottom: 30px;
}

.chart-container {
background: white;
border-radius: 12px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
padding: 20px;
height: 400px;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}

.chart-container:hover {
transform: translateY(-5px);
box-shadow: 0 12px 25px rgba(0, 0, 0, 0.15);
}

.chart-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}

.chart-title h2 {
font-size: 1.3rem;
color: #2c3e50;
display: flex;
align-items: center;
gap: 8px;
}

.chart {
flex: 1;
width: 100%;
}

.loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
}

.spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(52, 152, 219, 0.2);
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

.error {
color: #e74c3c;
text-align: center;
padding: 20px;
}

.refresh-btn {
background: #3498db;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
display: flex;
align-items: center;
gap: 5px;
}

.refresh-btn:hover {
background: #2980b9;
}

.footer {
text-align: center;
padding: 25px;
color: #7f8c8d;
font-size: 1rem;
background: white;
border-radius: 0 0 12px 12px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
}

.stats-bar {
display: flex;
justify-content: space-around;
background: white;
padding: 15px;
border-radius: 10px;
margin-bottom: 25px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}

.stat-card {
text-align: center;
padding: 15px;
}

.stat-card h3 {
font-size: 2.5rem;
color: #3498db;
margin-bottom: 5px;
}

.stat-card p {
color: #7f8c8d;
font-size: 0.95rem;
}

.chart-icon {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: #e3f2fd;
border-radius: 8px;
color: #3498db;
}

/* 响应式设计 */
@media (max-width: 1200px) {
.chart-grid {
grid-template-columns: repeat(2, 1fr);
}
}

@media (max-width: 768px) {
.chart-grid {
grid-template-columns: 1fr;
}

.header {
flex-direction: column;
text-align: center;
gap: 15px;
}

.stats-bar {
flex-direction: column;
}
}
</style>
</head>
<body>
<div id="app">
<div class="header">
<h1><i class="fas fa-chart-line"></i> 9种图表展示系统（前后端交互版）</h1>
<div class="controls">
<el-button type="primary" @click="refreshAllCharts" icon="el-icon-refresh">刷新所有图表</el-button>
<el-button type="success" @click="showDataSourceInfo" icon="el-icon-info">数据来源说明</el-button>
</div>
</div>

<div class="stats-bar">
<div class="stat-card">
<h3>9</h3>
<p>图表类型</p >
</div>
<div class="stat-card">
<h3>{{ totalDataPoints }}</h3>
<p>数据点总量</p >
</div>
<div class="stat-card">
<h3>{{ lastUpdated }}</h3>

<p>最后更新时间</p >
</div>
<div class="stat-card">
<h3>5</h3>
<p>模拟API接口</p >
</div>
</div>

<div class="chart-grid">
<!-- 折线图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-chart-line"></i></span> 销售趋势折线图</h2>
<button class="refresh-btn" @click="fetchLineChartData(0)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="lineChart"></div>
<div class="loading" v-if="loadingStatus[0]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[0]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchLineChartData(0)">重试</el-button>
</div>
</div>

<!-- 散点图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-braille"></i></span> 产品分布散点图</h2>
<button class="refresh-btn" @click="fetchScatterChartData(1)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="scatterChart"></div>
<div class="loading" v-if="loadingStatus[1]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[1]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchScatterChartData(1)">重试</el-button>
</div>
</div>

<!-- 箱线图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-box"></i></span> 质量分布箱线图</h2>
<button class="refresh-btn" @click="fetchBoxplotChartData(2)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="boxplotChart"></div>
<div class="loading" v-if="loadingStatus[2]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[2]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchBoxplotChartData(2)">重试</el-button>
</div>
</div>

<!-- 热力图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-fire"></i></span> 用户行为热力图</h2>
<button class="refresh-btn" @click="fetchHeatmapChartData(3)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="heatmapChart"></div>
<div class="loading" v-if="loadingStatus[3]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[3]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchHeatmapChartData(3)">重试</el-button>
</div>
</div>

<!-- 饼图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-chart-pie"></i></span> 市场份额饼图</h2>
<button class="refresh-btn" @click="fetchPieChartData(4)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="pieChart"></div>
<div class="loading" v-if="loadingStatus[4]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[4]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchPieChartData(4)">重试</el-button>
</div>
</div>

<!-- 柱形图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-chart-bar"></i></span> 月度收入柱形图</h2>
<button class="refresh-btn" @click="fetchBarChartData(5)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="barChart"></div>
<div class="loading" v-if="loadingStatus[5]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[5]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchBarChartData(5)">重试</el-button>
</div>
</div>

<!-- 雷达图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-bullseye"></i></span> 能力评估雷达图</h2>
<button class="refresh-btn" @click="fetchRadarChartData(6)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="radarChart"></div>
<div class="loading" v-if="loadingStatus[6]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[6]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchRadarChartData(6)">重试</el-button>
</div>
</div>

<!-- 树图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-sitemap"></i></span> 组织结构树图</h2>
<button class="refresh-btn" @click="fetchTreeChartData(7)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="treeChart"></div>
<div class="loading" v-if="loadingStatus[7]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[7]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchTreeChartData(7)">重试</el-button>
</div>
</div>

<!-- 漏斗图 -->
<div class="chart-container">
<div class="chart-title">
<h2><span class="chart-icon"><i class="fas fa-filter"></i></span> 销售漏斗图</h2>
<button class="refresh-btn" @click="fetchFunnelChartData(8)"><i class="fas fa-sync-alt"></i> 刷新</button>
</div>
<div class="chart" ref="funnelChart"></div>
<div class="loading" v-if="loadingStatus[8]">
<div class="spinner"></div>
<p>正在加载数据...</p >
</div>
<div class="error" v-if="errorStatus[8]">
<p>数据加载失败</p >
<el-button type="primary" size="small" @click="fetchFunnelChartData(8)">重试</el-button>
</div>
</div>
</div>

<div class="footer">
<p>图表展示系统  2023 | 共展示 {{ chartTypes.length }} 种图表类型 | 数据点总量: {{ totalDataPoints }} | 最后更新: {{ lastUpdated }}</p >
</div>
</div>

<script>
new Vue({
el: '#app',
data() {
return {
chartTypes: ['折线图', '散点图', '箱线图', '热力图', '饼图', '柱形图', '雷达图', '树图', '漏斗图'],
charts: [],
loadingStatus: Array(9).fill(true),
errorStatus: Array(9).fill(false),
totalDataPoints: 0,
lastUpdated: new Date().toLocaleTimeString()
};
},
mounted() {
// 初始化所有图表
this.initCharts();

// 获取所有图表数据
this.fetchAllChartData();
},
methods: {
// 初始化图表实例
initCharts() {
this.charts = [
echarts.init(this.$refs.lineChart),
echarts.init(this.$refs.scatterChart),
echarts.init(this.$refs.boxplotChart),
echarts.init(this.$refs.heatmapChart),
echarts.init(this.$refs.pieChart),
echarts.init(this.$refs.barChart),
echarts.init(this.$refs.radarChart),
echarts.init(this.$refs.treeChart),
echarts.init(this.$refs.funnelChart)
];

// 窗口大小改变时重新调整图表大小
window.addEventListener('resize', () => {
this.charts.forEach(chart => chart.resize());
});
},

// 获取所有图表数据
fetchAllChartData() {
this.fetchLineChartData(0);
this.fetchScatterChartData(1);
this.fetchBoxplotChartData(2);
this.fetchHeatmapChartData(3);
this.fetchPieChartData(4);
this.fetchBarChartData(5);
this.fetchRadarChartData(6);
this.fetchTreeChartData(7);
this.fetchFunnelChartData(8);
},

// 刷新所有图表
refreshAllCharts() {
this.loadingStatus = Array(9).fill(true);
this.errorStatus = Array(9).fill(false);
this.fetchAllChartData();
this.lastUpdated = new Date().toLocaleTimeString();
this.$message.success('所有图表数据已刷新！');
},

// 显示数据来源信息
showDataSourceInfo() {
this.$alert('本系统展示的所有数据均来自模拟API接口，模拟了真实的后端数据交互过程。在实际应用中，这些数据通常来自数据库、第三方API或实时数据流。', '数据来源说明', {
confirmButtonText: '确定',
customClass: 'info-alert'
});
},

// 模拟API请求
async mockApiRequest(data, delay = 800, successRate = 0.9) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < successRate) {
resolve(data);
} else {
reject(new Error('API请求失败'));
}
}, delay);
});
},

// 获取折线图数据
async fetchLineChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const response = await this.mockApiRequest({
categories: ['1月', '2月', '3月', '4月', '5月', '6月'],
series: [{
name: '产品A',
data: [125, 136, 128, 145, 162, 178]
}, {
name: '产品B',
data: [98, 112, 135, 156, 142, 165]
}]
});

this.renderLineChart(index, response);
this.totalDataPoints += response.series.reduce((sum, s) => sum + s.data.length, 0);
} catch (error) {
this.errorStatus[index] = true;
console.error('折线图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染折线图
renderLineChart(index, data) {
const option = {
title: { text: '月度销售趋势', left: 'center' },
tooltip: { trigger: 'axis' },
legend: { data: data.series.map(s => s.name), bottom: 10 },
grid: { left: '3%', right: '4%', bottom: '15%', top: '15%', containLabel: true },
xAxis: { type: 'category', data: data.categories },
yAxis: { type: 'value' },
series: data.series.map(s => ({
name: s.name,
type: 'line',
data: s.data,
smooth: true,
lineStyle: { width: 3 },
symbolSize: 8
}))
};

this.charts[index].setOption(option);
},

// 获取散点图数据
async fetchScatterChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const response = await this.mockApiRequest({
data: [
[10.0, 8.04], [8.0, 6.95], [13.0, 7.58], [9.0, 8.81], [11.0, 8.33],
[14.0, 9.96], [6.0, 7.24], [4.0, 4.26], [12.0, 10.84], [7.0, 4.82],
[5.0, 5.68], [15.0, 12.15], [16.0, 13.84], [18.0, 15.24], [20.0, 16.67],
[17.0, 14.88], [19.0, 16.04], [21.0, 17.78], [22.0, 19.25], [25.0, 22.15]
]
});

this.renderScatterChart(index, response);
this.totalDataPoints += response.data.length;
} catch (error) {
this.errorStatus[index] = true;
console.error('散点图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染散点图
renderScatterChart(index, data) {
const option = {
title: { text: '产品分布分析', left: 'center' },
xAxis: { name: '价格 (万元)', scale: true },
yAxis: { name: '销量 (千台)', scale: true },
tooltip: {
formatter: function (params) {
return `价格: ${params.value[0]}万<br>销量: ${params.value[1]}千台`;
}
},
series: [{
type: 'scatter',
data: data.data,
symbolSize: function (val) {
return Math.sqrt(val[1]) * 2;
},
itemStyle: {
color: function(params) {
const colors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'];
return colors[Math.floor(Math.random() * colors.length)];
}
}
}]
};

this.charts[index].setOption(option);
},

// 获取箱线图数据
async fetchBoxplotChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const response = await this.mockApiRequest({
categories: ['产品A', '产品B', '产品C', '产品D'],
data: [
[650, 750, 850, 950, 1050],
[550, 650, 750, 880, 1000],
[700, 800, 900, 1000, 1100],
[600, 720, 850, 920, 980]
]
});

this.renderBoxplotChart(index, response);
this.totalDataPoints += response.data.length * 5; // 每个箱线图有5个数据点
} catch (error) {
this.errorStatus[index] = true;
console.error('箱线图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染箱线图
renderBoxplotChart(index, data) {
const option = {
title: { text: '产品质量分布', left: 'center' },
tooltip: { trigger: 'item' },
xAxis: { type: 'category', data: data.categories },
yAxis: { type: 'value', name: '质量评分' },
series: [{
name: '质量分布',
type: 'boxplot',
data: data.data,
itemStyle: {
color: '#5470c6',
borderColor: '#314a9e'
}
}]
};

this.charts[index].setOption(option);
},

// 获取热力图数据
async fetchHeatmapChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const hours = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const days = ['0:00', '4:00', '8:00', '12:00', '16:00', '20:00'];

// 生成热力图数据
const data = [];
for (let i = 0; i < hours.length; i++) {
for (let j = 0; j < days.length; j++) {
data.push([i, j, Math.floor(Math.random() * 100)]);
}
}

const response = await this.mockApiRequest({
hours: hours,
days: days,
data: data
});

this.renderHeatmapChart(index, response);
this.totalDataPoints += response.data.length;
} catch (error) {
this.errorStatus[index] = true;
console.error('热力图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染热力图
renderHeatmapChart(index, data) {
const option = {
title: { text: '用户活跃度热力图', left: 'center' },
tooltip: {
position: 'top',
formatter: function (params) {
return `${data.hours[params.value[0]]} ${data.days[params.value[1]]}<br>活跃度: ${params.value[2]}`;
}
},
grid: { left: '3%', right: '4%', bottom: '10%', top: '15%', containLabel: true },
xAxis: { type: 'category', data: data.hours },
yAxis: { type: 'category', data: data.days },
visualMap: {
min: 0,
max: 100,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: 0,
inRange: {
color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
}
},
series: [{
type: 'heatmap',
data: data.data,
emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0, 0, 0, 0.5)' } }
}]
};

this.charts[index].setOption(option);
},

// 获取饼图数据
async fetchPieChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const response = await this.mockApiRequest({
data: [
{ value: 335, name: '产品A' },
{ value: 310, name: '产品B' },
{ value: 234, name: '产品C' },
{ value: 135, name: '产品D' },
{ value: 154, name: '产品E' }
]
});

this.renderPieChart(index, response);
this.totalDataPoints += response.data.length;
} catch (error) {
this.errorStatus[index] = true;
console.error('饼图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染饼图
renderPieChart(index, data) {
const option = {
title: { text: '市场份额分布', left: 'center' },
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
legend: { orient: 'vertical', right: 10, top: 'center' },
series: [{
name: '市场份额',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: { show: false },
emphasis: {
label: { show: true, fontSize: 18, fontWeight: 'bold' }
},
data: data.data
}]
};

this.charts[index].setOption(option);
},

// 获取柱形图数据
async fetchBarChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const response = await this.mockApiRequest({
categories: ['1月', '2月', '3月', '4月', '5月', '6月'],
series: [
{ name: '2022年', data: [65, 59, 80, 81, 56, 55] },
{ name: '2023年', data: [82, 70, 91, 94, 77, 85] }
]
});

this.renderBarChart(index, response);
this.totalDataPoints += response.series.reduce((sum, s) => sum + s.data.length, 0);
} catch (error) {
this.errorStatus[index] = true;
console.error('柱形图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染柱形图
renderBarChart(index, data) {
const option = {
title: { text: '月度收入对比 (万元)', left: 'center' },
tooltip: { trigger: 'axis' },
legend: { data: data.series.map(s => s.name), bottom: 10 },
grid: { left: '3%', right: '4%', bottom: '15%', top: '15%', containLabel: true },
xAxis: { type: 'category', data: data.categories },
yAxis: { type: 'value' },
series: data.series.map(s => ({
name: s.name,
type: 'bar',
data: s.data,
barWidth: '30%',
itemStyle: {
color: function(params) {
const colorList = ['#5470c6', '#91cc75'];
return colorList[params.seriesIndex];
}
}
}))
};

this.charts[index].setOption(option);
},

// 获取雷达图数据
async fetchRadarChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const response = await this.mockApiRequest({
indicators: [
{ name: '销售能力', max: 100 },
{ name: '技术能力', max: 100 },
{ name: '市场洞察', max: 100 },
{ name: '客户服务', max: 100 },
{ name: '团队协作', max: 100 },
{ name: '创新能力', max: 100 }
],
data: [
{ name: '团队A', value: [85, 70, 90, 75, 80, 95] },
{ name: '团队B', value: [70, 85, 75, 90, 65, 80] }
]
});

this.renderRadarChart(index, response);
this.totalDataPoints += response.data.length * response.indicators.length;
} catch (error) {
this.errorStatus[index] = true;
console.error('雷达图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染雷达图
renderRadarChart(index, data) {
const option = {
title: { text: '团队能力评估', left: 'center' },
tooltip: {},
legend: { data: data.data.map(d => d.name), bottom: 10 },
radar: {
indicator: data.indicators,
radius: '65%'
},
series: [{
type: 'radar',
data: data.data
}]
};

this.charts[index].setOption(option);
},

// 获取树图数据
async fetchTreeChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const response = await this.mockApiRequest({
name: '公司组织架构',
children: [
{
name: '技术部',
children: [
{ name: '前端组', value: 15 },
{ name: '后端组', value: 20 },
{ name: '测试组', value: 8 }
]
},
{
name: '市场部',
children: [
{ name: '销售组', value: 12 },
{ name: '推广组', value: 10 }
]
},
{
name: '行政部',
children: [
{ name: '人事组', value: 6 },
{ name: '财务组', value: 5 }
]
}
]
});

this.renderTreeChart(index, response);
// 树图数据点计算较为复杂，这里简单估算
this.totalDataPoints += 10;
} catch (error) {
this.errorStatus[index] = true;
console.error('树图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染树图
renderTreeChart(index, data) {
const option = {
title: { text: '公司组织结构', left: 'center' },
tooltip: { trigger: 'item', formatter: '{b}' },
series: [{
type: 'tree',
data: [data],
top: '10%',
bottom: '10%',
layout: 'orthogonal',
orient: 'vertical',
symbolSize: 7,
initialTreeDepth: 2,
label: {
position: 'top',
verticalAlign: 'middle',
align: 'right',
fontSize: 12
},
leaves: {
label: {
position: 'right',
verticalAlign: 'middle',
align: 'left'
}
},
emphasis: {
focus: 'descendant'
},
expandAndCollapse: true,
animationDuration: 550,
animationDurationUpdate: 750
}]
};

this.charts[index].setOption(option);
},

// 获取漏斗图数据
async fetchFunnelChartData(index) {
this.loadingStatus[index] = true;
this.errorStatus[index] = false;

try {
const response = await this.mockApiRequest({
data: [
{ value: 100, name: '访问量' },
{ value: 80, name: '点击量' },
{ value: 60, name: '咨询量' },
{ value: 40, name: '订单量' },
{ value: 20, name: '成交额' }
]
});

this.renderFunnelChart(index, response);
this.totalDataPoints += response.data.length;
} catch (error) {
this.errorStatus[index] = true;
console.error('漏斗图数据获取失败:', error);
} finally {
this.loadingStatus[index] = false;
}
},

// 渲染漏斗图
renderFunnelChart(index, data) {
const option = {
title: { text: '销售转化漏斗', left: 'center' },
tooltip: { trigger: 'item', formatter: '{a} <br/>{b} : {c}%' },
legend: { data: data.data.map(d => d.name), bottom: 10 },
series: [{
name: '转化率',
type: 'funnel',
left: '10%',
top: 60,
bottom: 60,
width: '80%',
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
label: { show: true, position: 'inside' },
labelLine: { length: 10, lineStyle: { width: 1, type: 'solid' } },
itemStyle: { borderColor: '#fff', borderWidth: 1 },
emphasis: { label: { fontSize: 16 } },
data: data.data
}]
};

this.charts[index].setOption(option);
}
}
});
</script>
</body>
</html>